/*******************************************************************************
 * Copyright (c) 2010 European Software Institute - Tecnalia.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Author - Adri�n Noguero (adrian.noguero@esi.es)
 *     
 *******************************************************************************/

package es.esi.gemde.modelvalidator.service.impl;

import java.util.List;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;

import es.esi.gemde.modelvalidator.exceptions.IllegalTargetException;
import es.esi.gemde.modelvalidator.exceptions.NoConstraintsValidatedException;
import es.esi.gemde.modelvalidator.exceptions.ValidationEngineException;
import es.esi.gemde.modelvalidator.service.IBatchValidation;
import es.esi.gemde.modelvalidator.service.IModelValidatorConstraint;
import es.esi.gemde.modelvalidator.service.IValidationResult;


/**
 * Implementation of the {@link IBatchValidation} interface
 *
 * @author Adrian Noguero (adrian.noguero@tecnalia.com)
 * @version 1.0
 * @since 1.0
 *
 */
public class BatchValidation implements IBatchValidation{

	private String id;
	private String description;
	private String name;
	private List<IModelValidatorConstraint> selectedConstraints;
	private ResourceType type;
	

	/**
	 * Constructor of the class.
	 * It is not intended to be used outside this plug-in. Instead users should use the {@link es.esi.gemde.modelvalidator.service.BatchValidationFactory}.
	 *
	 *@param name the name of the validation
	 *@param constraints the list of constraints that are selected for the current validation
	 *@throws IllegalArgumentException if any null parameter is provided
	 *@see es.esi.gemde.modelvalidator.service.BatchValidationFactory#createBatchValidation(String, List)
	 */
	public BatchValidation(String name, List<IModelValidatorConstraint> constraints, ResourceType type) throws IllegalArgumentException {
		// Check inputs
		if (name == null || constraints == null) {
			throw new IllegalArgumentException("A null input parameter was provided");
		}
		if (constraints.size() == 0) {
			throw new IllegalArgumentException("Constraint list must contain at least one constraint");
		}
		
		this.name = name;
		this.selectedConstraints = constraints;
		this.type = type;
	}
	
	/**
	 * Constructor of the class.
	 * It is not intended to be used outside this plug-in. Instead users should use the {@link es.esi.gemde.modelvalidator.service.BatchValidationFactory}.
	 *
	 *@param id the identifier of the validation
	 *@param name the name of the validation
	 *@param description a meaningful description of the validation
	 *@param constraints the list of constraints that are selected for the current validation
	 *@throws IllegalArgumentException if any null parameter is provided
	 *@see es.esi.gemde.modelvalidator.service.BatchValidationFactory#createBatchValidation(String, List)
	 *@since 1.4.0
	 */
	public BatchValidation(String id, String name, String description, List<IModelValidatorConstraint> constraints, ResourceType type) throws IllegalArgumentException {
		// Check inputs
		if (id == null || name == null || constraints == null) {
			throw new IllegalArgumentException("A null input parameter was provided");
		}
		if (constraints.size() == 0) {
			throw new IllegalArgumentException("Constraint list must contain at least one constraint");
		}
		
		this.id = id;
		this.description = description;
		this.name = name;
		this.selectedConstraints = constraints;
		this.type = type;
	}
	
	/* (non-Javadoc)
	 * @see es.esi.gemde.modelvalidator.service.IBatchValidation#execute()
	 */
	@Override
	public IValidationResult execute(List<EObject> onTargets) throws IllegalArgumentException, IllegalTargetException, ValidationEngineException, NoConstraintsValidatedException {
		// Check for null/empty values
		if (onTargets == null || onTargets.size() == 0) {
			throw new IllegalArgumentException("Null or empty target list was provided to IBatchValidation#exectue()");
		}
		
		// First check that all the targets refer  to the same metamodel
		EPackage targetedMM = null;
		for (EObject target : onTargets) {
			if (targetedMM == null) {
				targetedMM = target.eClass().getEPackage();
			}
			else if (!targetedMM.equals(target.eClass().getEPackage())) {
				throw new IllegalArgumentException("Selected targets do not refer to the same metamodel");
			}
		}
		
		// Then check that the validation targets are elements of the same metamodel as the constraints in the validation
		for (IModelValidatorConstraint current : selectedConstraints) {
			if (!targetedMM.equals(current.getApplicationMetamodel())) {
				throw new IllegalTargetException("Constraint '" + current.getName() + "' in Category '" + current.getCategory() + "' cannot target objects of type '" + targetedMM.getName() + "'. Validation aborted.");
			}
		}
		
		// If all the preconditions are met the validation process is launched
		// Status is updated with every validation in the batch
		// If an exception should raise, we wrap it in a ValidationEngineException.
		ValidationResult result = new ValidationResult();
		int status = IStatus.OK;
		for (EObject target : onTargets) {
			for (IModelValidatorConstraint current : selectedConstraints) {
				try {
					if (current.targetsTypeOf(target)) {
						IStatus currentResult = current.validate(target);
						if (currentResult.isOK()) {
							result.getSuccessfulConstraints().add(new ValidationTarget(current, target));
						}
						else {
							result.getFailedConstraints().add(new ValidationTarget(current, target));
							if (currentResult.getSeverity() == IStatus.ERROR) {
								status = IStatus.ERROR;
							}
							else if (currentResult.getSeverity() == IStatus.WARNING && status < IStatus.WARNING) {
								status = IStatus.WARNING;
							}
							else if (currentResult.getSeverity() == IStatus.INFO && status < IStatus.INFO) {
								status = IStatus.INFO;
							}
							result.addMessage(currentResult.getMessage());
						}
					}
				}
				catch (Exception e) {
					throw new ValidationEngineException(e);
				}
			}
		}
		
		if (result.getSuccessfulConstraints().size() + result.getFailedConstraints().size() == 0) {
			throw new NoConstraintsValidatedException("The validation '" + name + "' couldn't validate any constraint against the given targets");
		}
		
		// Update status and return
		result.setStatus(status);
		return result;
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modelvalidator.service.IBatchValidation#getSelectedConstraints()
	 */
	@Override
	public List<IModelValidatorConstraint> getSelectedConstraints() {
		return selectedConstraints;
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modelvalidator.service.IBatchValidation#getName()
	 */
	@Override
	public String getName() {
		return name;
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modelvalidator.service.IBatchValidation#isProtected()
	 */
	@Override
	public boolean isProtected() {
		return type.equals(ResourceType.PROTECTED);
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modelvalidator.service.IModelValidatorConstraint#getDescription()
	 */
	@Override
	public String getDescription() {
		return description;
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modelvalidator.service.IModelValidatorConstraint#getIdentifier()
	 */
	@Override
	public String getIdentifier() {
		return id;
	}

	@Override
	public ResourceType getResourceType() {
		return type;
	}
}
